// Texture.h
//
#ifndef TEXTURE_ONCE
#define TEXTURE_ONCE

#include "OpenGL.h"
#include <fstream>

namespace OpenGL {

// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
class TextureUtility
{
public:
	// ------------------------------------------------------------------------------------------------
	static int log2(unsigned int x)
	{
		int y = 0;
		while (x >>= 1) ++y;
		return y;
	}


	// ------------------------------------------------------------------------------------------------
	static bool isPowerOfTwo(unsigned int x)
	{
		return ((x & (x - 1)) == 0);
	}

	// ------------------------------------------------------------------------------------------------
	static unsigned int nextPowOfTwo(unsigned int x)
	{
		x--;
		x |= x >> 1;  // handle  2 bit numbers
		x |= x >> 2;  // handle  4 bit numbers
		x |= x >> 4;  // handle  8 bit numbers
		x |= x >> 8;  // handle 16 bit numbers
		x |= x >> 16; // handle 32 bit numbers
#if 0	
		// @TODO: Enable only for 64bit compilers
		x |= x >> 32; // handle 64 bit numbers
#endif

		x++;

		return x;
	}

	// ----------------------------------------------------------------------------------------------
	static 	void dumpTGA(std::string targaFilename, unsigned char *pixels, short numChannels, short w, short h)
	{
		//typedef struct {
		//   //char  idlength;
		//   //char  colourmaptype;
		//   //char  datatypecode;
		//   //short int colourmaporigin;
		//   //short int colourmaplength;
		//   //char  colourmapdepth;
		//   //short int x_origin;
		//   //short int y_origin;
		//   //short width;
		//   //short height;
		//   //char  bitsperpixel;
		//   //char  imagedescriptor;
		//	GLubyte Header[12]; //12 bytes
		//} TGA_HEADER;

		const char cTgaHeader[12] = { 0, 0, 2, 0,  0, 0, 0, 0,  0, 0, 0, 0 };

		std::ofstream file;

		// Open the file for output
		file.open(targaFilename.c_str(), std::ios::out | std::ios::binary | std::ios::trunc); 

		if (!file)
			return;

		// swap RGB -> BGR
		char *pxl = new char[w*h*numChannels];
		memcpy(pxl,pixels,sizeof(char)*w*h*numChannels);
		for(int i=0; i<w*h*numChannels; i+=numChannels)
		{
			pxl[i + 0] = pxl[i + 0] ^ pxl[i + 2];
			pxl[i + 2] = pxl[i + 0] ^ pxl[i + 2];
			pxl[i + 0] = pxl[i + 0] ^ pxl[i + 2];
		}

		// Create some temporaries
		char bitdepth = numChannels * 8;
		char bituse = 0;

		// Write out all targa image data
		file.write(cTgaHeader, 12 * sizeof(char));

		file.write((char*)&w, sizeof(short));
		file.write((char*)&h, sizeof(short));
		file.write(&bitdepth, sizeof(char));
		file.write(&bituse, sizeof(char));

		file.write((char*)pxl, w * h * numChannels * sizeof(char));

		// Close file again
		file.close();
		delete [] pxl;
	}

};

// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
class Texture
{
protected:
	GLuint	textureID;
	GLenum  dataFormat;
	GLenum  dataType;
	bool    valid;

public:
	Texture() : textureID(0),valid(false),dataFormat(0),dataType(0) {}
	virtual ~Texture() { destroy(); }

	// Create empty Texture
	virtual void	create()
	{
		destroy();
		glGenTextures(1, &textureID);
		glBindTexture(getTarget(),textureID);
	}

	// Create Texture 
	//virtual void create(int mipMapLevel,
	//	int numChannels,
	//	GLenum format,
	//	GLenum type,
	//	bool border,
	//	int width,
	//	int height,
	//	void *pixels) = 0;


	// Delete Texture
	virtual void	destroy()
	{
		if(textureID != 0)
		{
			glDeleteTextures(1, &textureID);
			textureID = 0;
		}
		valid = false;
	}

	// Custom methods
	virtual void	setParameter(GLenum pname, GLint i)		const 	{ glTexParameteri(getTarget(), pname, i);   }
	virtual void	setParameter(GLenum pname, GLfloat f)	const 	{ glTexParameterf(getTarget(), pname, f);   }
	virtual void	setParameter(GLenum pname, GLint *ip)	const 	{ glTexParameteriv(getTarget(), pname, ip); }
	virtual void	setParameter(GLenum pname, GLfloat *fp)	const 	{ glTexParameterfv(getTarget(), pname, fp); }

	
	virtual void dump( const std::string &filename ) {}

	virtual void	bind()		const { glBindTexture(getTarget(),textureID); }
	virtual void	unbind()	const { glBindTexture(getTarget(),0); }
	virtual bool	isValid()	const { return valid; }

	unsigned int	id()	const {return textureID;}

	virtual GLenum	getTarget() const = 0;
};
/////////////////////////////////////////////////////////////////////////////



////////////////////////////////////
// Texture 1D Class
class Texture1D : public Texture
{
protected:
	int texWidth;
	int texChannels;

	void create(int mipMapLevel,
		int numChannels,
		GLenum format,
		GLenum type,
		bool border,
		int width,
		int height,
		void *pixels)
	{}

public:	
	Texture1D() : Texture()	{texWidth = 0;}

	GLsizei	width()	const {return texWidth;}
	GLenum	getTarget()	const {return GL_TEXTURE_1D;}

	void create(int mipMapLevel,
		int numChannels,
		GLenum format,
		GLenum type,
		bool border,
		int width,
		void *pixels)
	{
		Texture::create();

		texWidth    = width;
		texChannels = numChannels;
		dataFormat  = format;
		dataType    = type;

		glTexImage1D(GL_TEXTURE_1D, mipMapLevel, format, width, border, format, type, pixels);
		valid = glValidate;
	}

	void clear()
	{
		glBindTexture(GL_TEXTURE_1D, textureID);
		
		unsigned char *pxl = new unsigned char[texWidth*1*texChannels];
		memset(pxl,0,texWidth*1*texChannels);
		glTexSubImage1D(GL_TEXTURE_1D, 0,0, texWidth, dataFormat, dataType,pxl);
		delete [] pxl;
		
		valid = glValidate;
	}

	virtual void dump( const std::string &filename ) {}
};


//////////////////////////////////////
// Texture 2D Class
class Texture2D : public Texture
{
protected:
	int texWidth;
	int texHeight;
	int texChannels;
	float u,v;

	// -----------------------------------------------------------------------------------------------
	void setDefaultFilterParameters()
	{
		if( !valid )
			return;
		
		setParameter( GL_TEXTURE_MIN_FILTER, GL_NEAREST );
		setParameter( GL_TEXTURE_MAG_FILTER, GL_NEAREST );
	}

public:	
	Texture2D() : Texture(), texWidth(0), texHeight(0), u(1.f), v(1.f) {}

	float getMaxU()		const {return u;}
	float getMaxV()		const {return v;}
	int	  width()	    const {return texWidth;}
	int	  height()	    const {return texHeight;}
	GLenum	getTarget()	const {return GL_TEXTURE_2D;}

	// -----------------------------------------------------------------------------------------------
	void create(int mipMapLevel,
		int numChannels,
		GLenum format,
		GLenum type,
		bool border,
		int width,
		int height,
		void *pixels)
	{
		Texture::create();
		
		texWidth	= width;
		texHeight	= height;
		texChannels = numChannels;
		dataFormat  = format;
		dataType    = type;

		glTexImage2D(GL_TEXTURE_2D, (GLint)mipMapLevel, (GLint)numChannels, (GLsizei)texWidth, (GLsizei)texHeight, (GLint)border, dataFormat, dataType, pixels);
		//glTexImage2D(GL_TEXTURE_RECTANGLE_EXT, (GLint)mipMapLevel, (GLint)numChannels, (GLsizei)texWidth, (GLsizei)texHeight, (GLint)border, format, type, pixels);
		valid = glValidate;

		setDefaultFilterParameters();
	}

	// -----------------------------------------------------------------------------------------------
	void createMipMap(int numChannels,
		GLenum format,
		GLenum type,
		int width,
		int height,
		void *pixels)

	{
		Texture::create();

		texWidth	= width;
		texHeight	= height;
		texChannels = numChannels;
		dataFormat  = format;
		dataType    = type;


		gluBuild2DMipmaps(GL_TEXTURE_2D,(GLint)numChannels, (GLsizei)texWidth, (GLsizei)texHeight, format, type, pixels);
		valid = glValidate;

		setDefaultFilterParameters();
	}

	// -----------------------------------------------------------------------------------------------
	void createFromFrameBuffer( int width, int height )
	{
		// copy the framebuffer pixels to a texture
		Texture::create();

		texWidth	= width;
		texHeight	= height;
		texChannels = 4;
		dataFormat  = GL_RGBA8;
		dataType    = GL_UNSIGNED_BYTE;

		glCopyTexImage2D(GL_TEXTURE_2D, 0, dataFormat, 0, 0, texWidth, texHeight, 0 );
		valid = glValidate;

		setDefaultFilterParameters();
	}

	// -----------------------------------------------------------------------------------------------
	void update(int xOffset,
		int yOffset,
		int width,
		int height,
		void *pixels)
	{
		bind();
		glTexSubImage2D(GL_TEXTURE_2D,0, xOffset, yOffset, width, height, dataFormat, dataType, pixels);
		valid = glValidate;
		unbind();
	}

	// -----------------------------------------------------------------------------------------------
	void clear()
	{
		glBindTexture(GL_TEXTURE_2D, textureID);
		
		unsigned char *pxl = new unsigned char[texWidth*texHeight*texChannels];
		memset(pxl,0,texWidth*texHeight*texChannels);
		glTexSubImage2D(GL_TEXTURE_2D, 0,0,0, texWidth, texHeight, dataFormat, dataType,pxl);
		delete [] pxl;

		u     = 1.f;
		v     = 1.f;
		valid = glValidate;
	}

	//void setMaxU(float U) { u = U; }
	void setMaxWidth(int w)   { u = (float)w/(float)texWidth; }
	//void setMaxV(float V) { v = V; }
	void setMaxHeight(int h)   { v = (float)h/(float)texHeight; }

	// -----------------------------------------------------------------------------------------------
	virtual void dump( const std::string &filename )
	{
		bind();

		unsigned char *data = new unsigned char[texWidth*texHeight*4];
		glGetTexImage( GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data );

		TextureUtility::dumpTGA( filename, data, 4, texWidth, texHeight );

		delete [] data;
		unbind();
	}
};

}

#endif